#ifndef __CODEC_H
#define __CODEC_H

#include "./types.h"
#include "./byte_view.h"
#include <string.h>

namespace utils::codec {

using namespace types;

template <typename T> inline void encode_int(uint8_t *buf, T value) {
    static_assert(IsInt<T>, "invalid type");

    if constexpr (IsIntSize<T, 1>) {
        buf[0] = static_cast<uint8_t>(value);
    } else if constexpr (IsIntSize<T, 2>) {
        buf[0] = static_cast<uint8_t>(value >> 8);
        buf[1] = static_cast<uint8_t>(value);
    } else if constexpr (IsIntSize<T, 4>) {
        buf[0] = static_cast<uint8_t>(value >> 24);
        buf[1] = static_cast<uint8_t>(value >> 16);
        buf[2] = static_cast<uint8_t>(value >> 8);
        buf[3] = static_cast<uint8_t>(value);
    }
}

template <typename T> inline T decode_int(const uint8_t *buf) {
    static_assert(IsInt<T>, "invalid type");

    if constexpr (IsIntSize<T, 1>) {
        return static_cast<T>(buf[0]);
    } else if constexpr (IsIntSize<T, 2>) {
        return (static_cast<T>(buf[0]) << 8) + static_cast<T>(buf[1]);
    } else if constexpr (IsIntSize<T, 4>) {
        return (static_cast<T>(buf[0]) << 24) + (static_cast<T>(buf[1]) << 16) +
               (static_cast<T>(buf[2]) << 8) + static_cast<T>(buf[3]);
    }
}

inline void encode_byte_view(uint8_t *buf, const ByteView &bw) {
    buf[0] = static_cast<uint8_t>(bw.len);
    memcpy(buf + 1, bw.buf, bw.len);
}

template <typename T, typename... Ts>
inline constexpr bool is_all_int() {
    using TYPE = std::decay_t<T>;

    if constexpr (IsInt<TYPE>) {
        if constexpr (sizeof...(Ts) != 0) {
            return is_all_int<Ts...>();
        } else {
            return true;
        }
    } else {
        return false;
    }
}

inline size_t value_size() {
    return 0;
}

template <typename T, typename... Ts>
inline size_t value_size(const T& value, const Ts&... ts) {
    using TYPE = std::decay_t<T>;
    
    static_assert(IsInt<T> || std::is_same_v<TYPE, ByteView>, "invalid type");

    if constexpr (IsInt<TYPE>) {
        if constexpr (sizeof...(Ts) != 0) {
            return sizeof(TYPE) + value_size(ts...);
        } else {
            return sizeof(TYPE);
        }
    } else if constexpr (std::is_same_v<TYPE, ByteView>) {
        if constexpr (sizeof...(Ts) != 0) {
            return value.len + 1 + value_size(ts...);
        } else {
            return value.len + 1;
        }
    }
}

template <typename T, typename... Ts>
inline constexpr size_t constexpr_value_size() {
    using TYPE = std::decay_t<T>;

    static_assert(IsInt<T>, "invalid type");

    if constexpr (sizeof...(Ts) != 0) {
        return sizeof(TYPE) + constexpr_value_size<Ts...>();
    } else {
        return sizeof(TYPE);
    }
}

template <typename T, typename... Ts>
inline void encode(uint8_t *buf, const T& value, const Ts&... ts) {
    using TYPE = std::decay_t<T>;
    static_assert(IsInt<T> || std::is_same_v<TYPE, ByteView>, "invalid type");

    if constexpr (IsInt<TYPE>) {
        encode_int(buf, value);
        if constexpr (sizeof...(Ts) != 0) {
            encode(buf + sizeof(TYPE), ts...);
        } 
    } else if constexpr (std::is_same_v<TYPE, ByteView>) {
        encode_byte_view(buf, value);
        if constexpr (sizeof...(Ts) != 0) {
            encode(buf + 1 + value.len, ts...);
        }
    }
}

template <typename T, typename... Ts>
inline void decode_int_all(const uint8_t *buf, T& value, Ts&... ts) {
    using TYPE = std::decay_t<T>;
    static_assert(IsInt<T>, "invalid type");

    value = decode_int<TYPE>(buf);
    if constexpr (sizeof...(Ts) != 0) {
        decode_int_all(buf + sizeof(TYPE), ts...);
    }
}

template <typename T, typename... Ts>
inline bool decode(const uint8_t *buf, size_t len, T& value, Ts&... ts) {
    using TYPE = std::decay_t<T>;
    static_assert(IsInt<T> || std::is_same_v<TYPE, ByteView>, "invalid type");

    if constexpr (is_all_int<T, Ts...>()) {
        constexpr size_t LEN = constexpr_value_size<T, Ts...>();
        if (LEN >= len) {
            return false;
        }
        decode_int_all(buf, value, ts...);
        return true;
    } else {
        if constexpr (IsInt<TYPE>) {
            if (sizeof (TYPE) > len) {
                return false;
            }
            value = decode_int<TYPE>(buf);
            if constexpr (sizeof...(Ts) != 0) {
                return decode(buf + sizeof(TYPE), len - sizeof(TYPE), ts...);
            } else {
                return true;
            }
        } else if constexpr (std::is_same_v<TYPE, ByteView>) {
            if (len < 1) {
                return false;
            }
            value.len = buf[0];
            if (len < (1 + value.len)) {
                return false;
            }
            value.buf = buf + 1;
            if constexpr (sizeof...(Ts) != 0) {
                return decode(buf + 1 + value.len, len - 1 - value.len, ts...);
            } else {
                return true;
            }
        }
    }
}


} // namespace utils::codec

#endif
